# Copyright (c) 2017 Presto Labs Pte. Ltd.
# Author: jaewon
# NOTE: Imported and modified from https://github.com/cyberdelia/atomic

from cffi import FFI
from functools import total_ordering
from typing import Optional

ffi = FFI()

ffi.cdef('''
void long_store(long *, long *);
long long_get_and_add(long *, long);
long long_get_and_sub(long *, long);
long long_get_and_set(long *, long);
long long_compare_and_set(long *, long *, long);
''')

atomic = ffi.verify('''
void long_store(long *v, long *n) {
  __atomic_store(v, n, __ATOMIC_SEQ_CST);
};
long long_get_and_add(long *v, long i) {
  return __atomic_fetch_add(v, i, __ATOMIC_SEQ_CST);
};
long long_get_and_sub(long *v, long i) {
  return __atomic_fetch_sub(v, i, __ATOMIC_SEQ_CST);
};
long long_get_and_set(long *v, long n) {
  return __atomic_exchange_n(v, n, __ATOMIC_SEQ_CST);
};
long long_compare_and_set(long *v, long *e, long n) {
  return __atomic_compare_exchange_n(v, e, n, 0, __ATOMIC_SEQ_CST,
                                     __ATOMIC_SEQ_CST);
};
''')


@total_ordering
class AtomicLong(object):
  def __init__(self, value: Optional[int] = None):
    self._value = ffi.new('long *', value)

  def __repr__(self):
    return '<{0} at 0x{1:x}: {2!r}>'.format(self.__class__.__name__, id(self), self.value)

  @property
  def value(self) -> int:
    return self._value[0]

  @value.setter
  def value(self, new: int) -> int:
    atomic.long_store(self._value, ffi.new('long *', new))

  def __iadd__(self, inc: int):
    atomic.long_get_and_add(self._value, inc)
    return self

  def __isub__(self, dec: int):
    atomic.long_get_and_sub(self._value, dec)
    return self

  def get_and_add(self, inc: int) -> int:
    return atomic.long_get_and_add(self._value, inc)

  def get_and_sub(self, dec: int) -> int:
    return atomic.long_get_and_sub(self._value, dec)

  def get_and_set(self, new_value: int) -> int:
    return atomic.long_get_and_set(self._value, new_value)

  def swap(self, new_value: int) -> int:
    return self.get_and_set(new_value)

  def compare_and_set(self, expect_value: int, new_value: int) -> bool:
    return bool(atomic.long_compare_and_set(self._value, ffi.new('long *', expect_value),
                                            new_value))

  def __eq__(self, a) -> bool:
    if self is a:
      return True
    elif isinstance(a, AtomicLong):
      return self.value == a.value
    else:
      return self.value == a

  def __ne__(self, a) -> bool:
    return not (self == a)

  def __lt__(self, a) -> bool:
    if self is a:
      return False
    elif isinstance(a, AtomicLong):
      return self.value < a.value
    else:
      return self.value < a
